package roslan.musicbox.server.data;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;

public class MusicTagSearchEngine {
	private static final double TITLE_FAC = 0.3;
	private static final double FILENAME_FAC = 0.25;
	private static final double ARTIST_FAC = 0.1;
	private static final double ALBUM_FAC = 0.2;
	private static final double YEAR_FAC = 0.1;
	private static final double TRACKNO_FAC = 0.05;
	
	private static final double ACCEPT_BOUND = 0.20;

	private ArrayList<ServerMusicTag> tagList;
	private HashMap<String, ArrayList<ServerMusicTag> > gramMap;
	
	private static final int MAX_RESULT = 20;
	
	class SearchResult {
		public ServerMusicTag tag;
		public double score;
		public SearchResult(){
			tag = null;
			score = 0;
		}
		public SearchResult(ServerMusicTag tag, double score){
			this.tag = tag;
			this.score = score;
		}
		public ServerMusicTag getTag() {
			return tag;
		}
		public void setTag(ServerMusicTag tag) {
			this.tag = tag;
		}
		public double getScore() {
			return score;
		}
		public void setScore(double score) {
			this.score = score;
		}
	}
	
	class ResultRanker implements Comparator<SearchResult>{
		public int compare(SearchResult r1, SearchResult r2) {
			if(r1.score > r2.score){
				return -1;
			}else{
				return 1;
			}
		}
		
	}
		
	public MusicTagSearchEngine(){
		tagList = new ArrayList<ServerMusicTag>();
		tagList.clear();
	}
	
	public void addMusicTag(ServerMusicTag tag){
		if(tag.getTitle() == null){
			tag.setTitle("");
		}
		if(tag.getAlbum() == null){
			tag.setAlbum("");
		}
		if(tag.getArtist() == null){
			tag.setArtist("");
		}
		if(tag.getYear() == null){
			tag.setYear("");
		}
		if(tag.getGenre() == null){
			tag.setGenre("");
		}
		if(tag.getFullpath() == null){
			tag.setFullpath("");
		}
		if(tag.getComment() == null){
			tag.setComment("");
		}
		if(tag.getTrackNumber() == null){
			tag.setTrackNumber("");
		}
		tagList.add(tag);
	}
	
	public int size(){
		return tagList.size();
	}
	
	public void clear(){
		tagList.clear();
	}
	
	public void buildGramMap(){
		System.out.println("Building Gram Map...");
		gramMap = new HashMap<String, ArrayList<ServerMusicTag> >();
		gramMap.clear();
		for(ServerMusicTag tag : tagList){
			String info = tag.getTitle() + tag.getAlbum() + tag.getArtist();
			info = preprocess(info);
			HashSet<String> gramSet = new HashSet<String>();
			gramSet.clear();
			for(int i=0; i<info.length()-1; ++i){
				gramSet.add(info.substring(i,i+2));
			}
			for(String gram : gramSet){
				if(gramMap.containsKey(gram)){
					gramMap.get(gram).add(tag);
				}else{
					ArrayList<ServerMusicTag> tagSet = new ArrayList<ServerMusicTag>();
					tagSet.clear();
					tagSet.add(tag);
					gramMap.put(gram, tagSet);
				}
			}
		}
//		try{
//			PrintWriter writer = new PrintWriter("E:/gram.txt");
//			for(String gram : gramMap.keySet()){
//				writer.println(gram+" "+gramMap.get(gram).size());
//			}
//			writer.flush();
//			writer.close();
//		}catch(Exception e){}
		System.out.println("Gram Map Building Succeeded!");
	}
	
	private String preprocess(String str){
		if(str == null) return "";
		if(str.isEmpty()) return str;
		str = str.toLowerCase();
		str = str.replaceAll("\\p{Space}", "");
		if(str.endsWith(".mp3")){
			str = str.substring(0,str.lastIndexOf(".mp3"));
		}
		str.replaceAll("\\([^\\(\\)]*\\)", "");
		return str;
	}
	
	//Str1 is the standard data
	private double calcEDScore(String str1, String str2, double factor){
		str1 = preprocess(str1);
		str2 = preprocess(str2);
		if(str1 == null || str2 == null) return 0;
		if(str1.isEmpty() || str2.isEmpty()) return 0;
		int ed = calcEditDistance(str1, str2);
		int lcs = calcLCS(str1, str2);
		double EDscore = 1 - ((double)ed)/(str1.length());
		if(EDscore < 0) EDscore = 0;
		double LCSscore = ((double)lcs)/(str1.length());
		double score = (EDscore + LCSscore)/2;
		return score*factor;
	}
	
	private int calcLCS(String str1, String str2){
		int len1 = str1.length();
		int len2 = str2.length();
		int[][] lcs = new int[len1+1][len2+1];
		for(int i=0;i<len1+1;++i){
			lcs[i][0] = 0;
		}
		for(int i=0;i<len2+1;++i){
			lcs[0][i] = 0;
		}
		for(int i=1;i<len1+1;++i){
			for(int j=1;j<len2+1;++j){
				lcs[i][j] = 0;
				if(lcs[i][j] < lcs[i-1][j]){
					lcs[i][j] = lcs[i-1][j];
				}
				if(lcs[i][j] < lcs[i][j-1]){
					lcs[i][j] = lcs[i][j-1];
				}
				int t = (str1.charAt(i-1) == str2.charAt(j-1)) ? 1 : 0;
				if(lcs[i][j] < lcs[i-1][j-1] + t){
					lcs[i][j] = lcs[i-1][j-1] + t;
				}
			}
		}
		return lcs[len1][len2];
	}
	
	private int calcEditDistance(String str1, String str2){
		int len1 = str1.length();
		int len2 = str2.length();
		int[][] ed = new int[len1+1][len2+1];
		for(int i=0;i<len1+1;++i){
			ed[i][0] = i;
		}
		for(int i=0;i<len2+1;++i){
			ed[0][i] = i;
		}
		for(int i=1;i<len1+1;++i){
			for(int j=1;j<len2+1;++j){
				ed[i][j] = 10000;
				if(ed[i][j] > ed[i-1][j]+1){
					ed[i][j] = ed[i-1][j]+1;
				}
				if(ed[i][j] > ed[i][j-1]+1){
					ed[i][j] = ed[i][j-1]+1;
				}
				int t = (str1.charAt(i-1) == str2.charAt(j-1)) ? 0 : 1;
				if(ed[i][j] > ed[i-1][j-1] + t){
					ed[i][j] = ed[i-1][j-1] + t;
				}
			}
		}
		return ed[len1][len2];
	}
	
	private double calcCmpScore(String str1, String str2, double factor){
		str1 = preprocess(str1);
		str2 = preprocess(str2);
		if(str1 == null || str2 == null) return 0;
		if(str1.isEmpty() || str2.isEmpty()) return 0;
		if(compareNumber(str1,str2)){
			return factor;
		}else{
			return 0;
		}
	}
	
	private boolean compareNumber(String str1, String str2){
		try{
			int v1 = Integer.valueOf(str1);
			int v2 = Integer.valueOf(str2);
			return v1 == v2;
		}catch(Exception e){
			return false;
		}
	}
	
	public ArrayList<ServerMusicTag> search(ServerMusicTag searchTag){
		ArrayList<SearchResult> candidate = new ArrayList<SearchResult>();
		candidate.clear();
		System.out.println(searchTag);
		
		String info = searchTag.getTitle() + searchTag.getAlbum() +
					  searchTag.getArtist() + searchTag.getFullpath();
		info = preprocess(info);
		HashSet<ServerMusicTag> candSet = new HashSet<ServerMusicTag>();
		candSet.clear();
		for(int i=0; i< info.length() - 1; ++i){
			String gram = info.substring(i, i+2);
			if(gramMap.containsKey(gram)){
				candSet.addAll(gramMap.get(gram));
			}
		}
		System.out.println("# of Candidates: " + candSet.size());
		
		for(ServerMusicTag tag : candSet){
			double totalScore = 0;
			totalScore += calcEDScore(tag.getTitle(), searchTag.getTitle(), TITLE_FAC);
			totalScore += calcEDScore(tag.getTitle(), searchTag.getFullpath(), FILENAME_FAC);
			totalScore += calcEDScore(tag.getAlbum(), searchTag.getAlbum(), ALBUM_FAC);
			totalScore += calcEDScore(tag.getArtist(), searchTag.getArtist(), ARTIST_FAC);
			totalScore += calcCmpScore(tag.getYear(), searchTag.getYear(), YEAR_FAC);
			totalScore += calcCmpScore(tag.getTrackNumber(), searchTag.getTrackNumber(), TRACKNO_FAC);
			if(totalScore > ACCEPT_BOUND){
				candidate.add(new SearchResult(tag, totalScore));
			}
		}

		Collections.sort(candidate, new ResultRanker());
		ArrayList<ServerMusicTag> result = new ArrayList<ServerMusicTag>();
		if(!candidate.isEmpty()){
			System.out.println(candidate.get(0).score + "    " + candidate.get(0).tag);
		}
		for(SearchResult res : candidate){
			result.add(res.tag);
			if(result.size() >= MAX_RESULT){
				break;
			}
		}
		return result;
	}
}
